/*
 * MIT License
 *
 * Copyright (c) 2024 恩爸编程
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.enba.boot.ex;

import com.enba.boot.core.base.Result;
import com.enba.boot.core.context.SecurityContextHolder;
import com.enba.boot.core.enums.StatusEnum;
import com.enba.boot.core.exception.AuthException;
import com.enba.boot.core.exception.BizException;
import com.enba.boot.core.exception.NotLoginException;
import java.lang.reflect.UndeclaredThrowableException;
import javax.naming.ServiceUnavailableException;
import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(basePackages = "com.enba")
@ResponseStatus(HttpStatus.OK)
@Order(2)
public class GlobalExceptionHandler {

  public GlobalExceptionHandler() {
    log.info("初始化全局异常处理器GlobalExceptionHandler");
  }

  /**
   * 这个异常通常在使用Spring框架时出现。当一个带有@Valid或@Validated注解的方法参数未能通过javax.validation提供的验证约束时，Spring
   * MVC会抛出MethodArgumentNotValidException。这个异常通常用于Web层，确保传递给控制器方法的参数是有效的。
   *
   * @param ex 异常信息
   * @return 返回结果
   */
  @ExceptionHandler({MethodArgumentNotValidException.class})
  public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
    log.error("参数校验异常:{}", ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(), ex);

    return Result.err(
        StatusEnum.PARAM_ERR.getCode(),
        ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
  }

  /**
   * 这个异常是由javax.validation
   * API定义的，在任何非Spring环境中都可能发生，或者在Spring环境中使用Validator接口进行验证时发生。当你直接使用Validator类来验证对象并且该对象未能通过验证时，会抛出ConstraintViolationException。
   *
   * @param ex exception
   * @return r
   */
  @ExceptionHandler({ConstraintViolationException.class})
  public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {
    log.error("参数校验异常:{}", ex.getConstraintViolations().iterator().next().getMessage(), ex);

    return Result.err(
        StatusEnum.PARAM_ERR.getCode(),
        ex.getConstraintViolations().iterator().next().getMessage());
  }

  /**
   * 处理自定义异常
   *
   * @param ex exception
   * @return r
   */
  @ExceptionHandler(BizException.class)
  public Result<?> handleBizException(BizException ex) {
    log.error("业务异常:{}", ex.getMessage(), ex);
    contextRemove();
    return Result.err(ex.getErrCode(), ex.getErrMsg());
  }

  /**
   * 未登录异常
   *
   * @param ex exception
   * @return r
   */
  @ExceptionHandler(NotLoginException.class)
  public Result<?> handleNotLoginException(NotLoginException ex) {
    log.error("未登录异常:{}", ex.getMessage(), ex);

    return Result.err(StatusEnum.NOT_LOGIN_ERR);
  }

  /**
   * 权限不足异常
   *
   * @param ex exception
   * @return r
   */
  @ExceptionHandler(AuthException.class)
  public Result<?> handleAuthException(AuthException ex) {
    log.error("权限不足异常:{}", ex.getMessage(), ex);
    contextRemove();
    return Result.err(StatusEnum.AUTH_ERR);
  }

  /**
   * 默认数据库异常处理
   *
   * @param ex exception
   * @return r
   */
  @ExceptionHandler(DataAccessException.class)
  public Result<?> handleDbException(DataAccessException ex) {
    log.error("执行数据库异常:{}", ex.getMessage(), ex);
    contextRemove();
    return Result.err(StatusEnum.DB_ERR);
  }

  /**
   * 拖低异常，其它ExceptionHandler处理不了的异常，交给托底异常处理，比如数据库异常等
   *
   * @param ex exception
   * @return r
   */
  @ExceptionHandler(Exception.class)
  public Result<?> handleException(Exception ex) {
    // 这里可以做一些业务级的处理，比如：推送钉钉消息通知等
    log.error("内部系统异常:{}", ex.getMessage(), ex);
    contextRemove();

    // 处理 UndeclaredThrowableException 情况
    Throwable realException = ex;
    if (ex instanceof UndeclaredThrowableException) {
      realException = ((UndeclaredThrowableException) ex).getUndeclaredThrowable();
    }

    // 现在可以处理真实的异常
    if (realException instanceof ServiceUnavailableException) {
      log.error("服务不可用异常: {}", realException.getMessage());
      return Result.err(503, realException.getMessage());
    }

    return Result.err(StatusEnum.ERR.getCode(), ex.getMessage());
  }

  /** 清理用户上下文信息 */
  private void contextRemove() {
    SecurityContextHolder.remove();
  }
}
